﻿/*! 
 * \mainpage Generating SID and STARs for controller files.
 *
 * \section Usage
 * You need to have Navigraph's PMDG SID/STARs installed. Just configure this
 * as Source directory and generate the files in the destination directory.
 * The Navigraph files are not included in this distribution. You need to license them.
 *
 * \section Requirements
 * .net 4.5 Framework, written in C# / WPF.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using SSG.Model;
using SSG.Tools;
using SSG.View;

namespace SSG
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        private Generator _generator = null;
        private MainWindowConsoleAndStatus _consoleAndStatus = null;
        private readonly Configuration _configuration = null;
        private readonly AboutBox _aboutWindow = new AboutBox();

        /// <summary>
        /// Constructor main window.
        /// </summary>
        public MainWindow()
        {
            this._configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
            this.InitMenuBindings();
            this.InitModel();
            this.InitializeComponent();
            this.DataContext = this._generator;
            this.PostInit();
            this.WriteStatusMsg(Properties.Resources.Initialized);
        }

        /// <summary>
        /// Init the model.
        /// </summary>
        private void InitModel()
        {
            this._consoleAndStatus = new MainWindowConsoleAndStatus(this);
            GeneratorSettings genSettings = Properties.Settings.Default.GeneratorSettings;
            if (genSettings == null) {
                this._generator = new Generator(new GeneratorSettings(this._consoleAndStatus));
                Properties.Settings.Default.GeneratorSettings = this._generator.Settings;
            }
            else {
                genSettings.ConsoleAndStatus = this._consoleAndStatus;
                this._generator = new Generator(genSettings);
                this.WriteStatusMsg(String.Format(Properties.Resources.SettingsReadFrom, this._configuration.FilePath));
            }
        }

        /// <summary>
        /// Menu bindings.
        /// </summary>
        private void InitMenuBindings()
        {
            CommandBinding binding = new CommandBinding(ApplicationCommands.Close);
            binding.Executed += this.CommandClose;
            binding.CanExecute += this.CommandCloseCanExecute;
            this.CommandBindings.Add(binding);
        }

        /// <summary>
        /// Post init tasks.
        /// </summary>
        private void PostInit()
        {
            string placeholders = Generator.Placeholders.EnumerationToString();
            this._lblFilenamePattern.ToolTip = placeholders;
            this._lblColourSct.ToolTip = placeholders;
            this._lblSctHeaderPattern.ToolTip = placeholders;
            if (this._generator.Settings.ExistsSidStarDirectory) this._generator.ReadAirportNavaidsAndRunways();
            this.SetAirportGroups();
        }

        /// <summary>
        /// Generator settings.
        /// </summary>
        private GeneratorSettings GeneratorSettings { get { return this._generator == null ? null : this._generator.Settings; } }

        /// <summary>
        /// Write status message.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="severity"> </param>
        private void WriteStatusMsg(string msg, ConsoleAndStatus.Severity severity = ConsoleAndStatus.Severity.Info)
        {
            if (this._consoleAndStatus == null) return;
            this._consoleAndStatus.WriteStatusMsg(msg, severity);
        }

        /// <summary>
        /// Write console message.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="severity"> </param>
        private void WriteConsoleMsg(string msg, ConsoleAndStatus.Severity severity = ConsoleAndStatus.Severity.Info)
        {
            if (this._consoleAndStatus == null) return;
            this._consoleAndStatus.WriteConsoleMsg(msg, severity);
        }

        #region Commands
        /// <summary>
        /// Command close executable?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CommandCloseCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        /// <summary>
        /// Command close current window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CommandClose(object sender, ExecutedRoutedEventArgs e)
        {
            this.Close();
        }
        #endregion Commands

        #region Events
        /// <summary>
        /// Select the source directory.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectDirectory(object sender, System.Windows.RoutedEventArgs e)
        {
            // entry checks
            if (this.GeneratorSettings == null) throw new NullReferenceException("Missing generator settings for directory selection");
            if (sender == null) throw new ArgumentNullException("sender");
            if (this._generator == null) throw new System.InvalidOperationException("Missing generator");

            // set new values
            FolderBrowserDialog fb = new FolderBrowserDialog {Description = Properties.Resources.SelectSourceDirectory, SelectedPath = this._generator.Settings.SourceDirectory, ShowNewFolderButton = true};
            if (fb.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                if (Equals(this._btnSelectSourceDirectory, sender))
                    this.GeneratorSettings.SourceDirectory = fb.SelectedPath;
                else if (Equals(this._btnSelectDestinationDirectory, sender))
                    this.GeneratorSettings.DestinationDirectory = fb.SelectedPath;
                else
                    throw new InvalidOperationException("Wrong sender for directory: " + sender);
            }
            this._generator.ReadAirportNavaidsAndRunways();
        }

        /// <summary>
        /// Refresh the SID /STAR airports, runways and navaids
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RefreshAirportsNavaidsAndRunways(object sender, System.Windows.RoutedEventArgs e)
        {
            // SourceDirectory already set by binding
            this._generator.ReadAirportNavaidsAndRunways();
        }

        /// <summary>
        /// Generate the destination files.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GenerateFiles(object sender, System.Windows.RoutedEventArgs e)
        {
            // entry checks
            if (this._generator == null) throw new System.InvalidOperationException("Missing generator");
            if (this._lbSidStarAirports.SelectedItems.Count < 1) {
                this.WriteStatusMsg(Properties.Resources.GenerateNoAirportsSelected, ConsoleAndStatus.Severity.Warning);
                return;
            }

            if (String.IsNullOrEmpty(this._tbFilenamePattern.Text)) {
                this.WriteStatusMsg(Properties.Resources.GenerateNoFileNamePattern, ConsoleAndStatus.Severity.Error);
                return;
            }

            // ReSharper disable LoopCanBeConvertedToQuery
            List<string> selectedAirports = new List<string>();
            foreach (var selectedAirport in this._lbSidStarAirports.SelectedItems) {
                selectedAirports.Add(selectedAirport.ToString());
            }
            // ReSharper restore LoopCanBeConvertedToQuery

            // generate
            try {
                this._generator.Settings.AirportGroups = this.GetAirportGroups(); // update groups
                this._generator.GenerateFiles(selectedAirports, this._tbFilenamePattern.Text);
            }
            catch (Exception ex) {
                this.WriteStatusMsg(String.Format(Properties.Resources.GenerateException, ex.Message), ConsoleAndStatus.Severity.Error);
                this.WriteConsoleMsg(ex.StackTrace, ConsoleAndStatus.Severity.Error);
            }
        }

        /// <summary>
        /// Reset the file pattern
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ResetFilePattern(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this._generator == null || this._generator.Settings == null) return;
            this._generator.Settings.SetDefaultFilePattern();
        }

        /// <summary>
        /// Reset the SCT Headerline pattern 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ResetSctHeaderlinePattern(object sender, RoutedEventArgs e)
        {
            if (this._generator == null || this._generator.Settings == null) return;
            this._generator.Settings.SetDefaultSctHeaderlinePattern();
        }

        /// <summary>
        /// Main window closing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WindowClosing(object sender, CancelEventArgs e)
        {
            Properties.Settings.Default.GeneratorSettings.AirportGroups = this.GetAirportGroups();
            Properties.Settings.Default.Save();
        }

        /// <summary>
        /// Settings directory executable?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdSettingsDirCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        /// <summary>
        /// Settings directory.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdSettingsDir(object sender, ExecutedRoutedEventArgs e)
        {
            if (this._configuration == null) return;
            string path = Path.GetDirectoryName(this._configuration.FilePath);
            if (String.IsNullOrEmpty(path)) return;
            Process.Start("explorer.exe", "\"" + path + "\""); // needs to be in quotes, contains ","
        }

        /// <summary>
        /// Settings delete executable?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdSettingsResetCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        /// <summary>
        /// Delete settings.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdSettingsReset(object sender, ExecutedRoutedEventArgs e)
        {
            // ReSharper disable PossibleUnintendedReferenceComparison
            if (this._configuration == null) return;
            if (e.Parameter == this._miFileDeleteAllSettings) {
                bool msg = false;
                if (File.Exists(this._configuration.FilePath)) {
                    File.Delete(this._configuration.FilePath);
                    this.WriteStatusMsg(String.Format(Properties.Resources.SettingsDeleted, this._configuration.FilePath));
                    msg = true;
                }
                this._generator = new Generator(new GeneratorSettings());
                this.DataContext = this._generator;
                Properties.Settings.Default.GeneratorSettings = this._generator.Settings;
                if (!msg) this.WriteStatusMsg(Properties.Resources.SettingsReset);
            }
            else if (e.Parameter == this._miFileResetGeneratingSettings) {
                if (this._generator == null || this._generator.Settings == null) return;
                this._generator.Settings.ResetGeneratingSettings();
                this.WriteStatusMsg(Properties.Resources.SettingsReset);
            }
            // ReSharper restore PossibleUnintendedReferenceComparison
        }

        /// <summary>
        /// About executable?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdAboutCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        /// <summary>
        /// About window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdAbout(object sender, ExecutedRoutedEventArgs e)
        {
            this._aboutWindow.ShowDialog();
        }

        /// <summary>
        /// Website executable?
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdWebsiteCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        /// <summary>
        /// Website.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CmdWebsite(object sender, ExecutedRoutedEventArgs e)
        {
            string url = "http://ssg.codeplex.com";
            // ReSharper disable PossibleUnintendedReferenceComparison
            if (e != null && e.Parameter == this._miHelpLegal)
                url += "/wikipage?title=Legal";
            else if (e != null && e.Parameter == this._miHelpLicense)
                url += "/license";
            // ReSharper restore PossibleUnintendedReferenceComparison

            Process.Start(url);
        }

        /// <summary>
        /// Clear the console
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClearConsole(object sender, System.Windows.RoutedEventArgs e)
        {
            this._tbConsole.Clear();
        }

        /// <summary>
        /// Open directory in Explorer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExplorerOpenDirectory(object sender, MouseButtonEventArgs e)
        {
            string dir = null;
            // ReSharper disable PossibleUnintendedReferenceComparison
            if (sender == this._lblSourceDirectory)
                dir = this._tbSourceDirectory.Text;
            else if (sender == this._lblDestinationDirectory || sender == this._btnShowDestinationDirectory)
                dir = this._tbDestinationDirectory.Text;
            // ReSharper restore PossibleUnintendedReferenceComparison

            if (String.IsNullOrEmpty(dir)) return;
            if (!Directory.Exists(dir)) return;
            Process.Start("explorer.exe", dir);
        }

        /// <summary>
        /// Open Explorer directory
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExplorerOpenDirectoryRoutedEvent(object sender, System.Windows.RoutedEventArgs e)
        {
            this.ExplorerOpenDirectory(sender, default(MouseButtonEventArgs));
        }

        /// <summary>
        /// Context menu in Aiports clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AirportsContextMenuClick(object sender, System.Windows.RoutedEventArgs e)
        {
            // ReSharper disable PossibleUnintendedReferenceComparison

            // Deselect only
            if (sender == this._miAirportsUnselectAll) {
                this._lbSidStarAirports.SelectedItems.Clear();
                return;
            }

            // anything selected, otherwise quit
            if (this._lbSidStarAirports.SelectedItems.Count < 1) return;

            // Open in editor
            if (sender == this._miAirportsOpenInEditor) {
                string icao = this._lbSidStarAirports.SelectedItem.ToString();
                string fileName = this.GeneratorSettings.SidStarDirectory + icao.ToUpperInvariant() + ".txt";
                System.Diagnostics.Process.Start(fileName);
                return;
            }

            // Add items
            bool addSelected = (sender == this._miAirportsAddSelectedGroup);
            TreeViewItem tvGroup = this.GetSelectedGroupTreeViewItem();
            // ReSharper restore PossibleUnintendedReferenceComparison

            if (tvGroup == null || !addSelected) {
                DialogGroupName dialog = new DialogGroupName {Owner = this};
                if (tvGroup != null) dialog.GroupName = tvGroup.Name;
                dialog.ShowDialog();
                if (dialog.DialogResult.HasValue && dialog.DialogResult.Value && !String.IsNullOrEmpty(dialog.GroupName)) {
                    string gn = dialog.GroupName;

                    // valid name?
                    if (!AirportsGroup.IsValidGroupName(gn)) {
                        this.WriteStatusMsg(Properties.Resources.GroupInvalidName, ConsoleAndStatus.Severity.Error);
                        return;
                    }

                    // already group by this name, otherwise create new
                    tvGroup = this.GetGroupByName(gn);
                    if (tvGroup == null) {
                        tvGroup = TreeViewUtil.GetTreeViewItem(gn, Properties.Resources.Folder_16x16);
                        this._tvGroupingAirports.Items.Add(tvGroup);
                        tvGroup.Tag = new AirportsGroup(gn);
                    }
                }
            }

            if (tvGroup == null || this._lbSidStarAirports.SelectedItems.Count < 1) return;

            // add selected airport ICAOs
            foreach (var selectedAirport in this._lbSidStarAirports.SelectedItems) {
                string icao = selectedAirport.ToString();
                AirportsGroup ag = tvGroup.Tag as AirportsGroup;
                if (ag == null) throw new InvalidOperationException("Missing " + typeof (AirportsGroup) + " for " + tvGroup.Name);
                if (ag.Icaos.Contains(icao)) continue;
                ag.Icaos.Add(icao);
                TreeViewItem tvAirport = TreeViewUtil.GetTreeViewItem(icao, Properties.Resources.AirportDOT16x16);
                tvGroup.Items.Add(tvAirport);
            }
        }

        /// <summary>
        /// Return selected group (if group), otherwise null.
        /// </summary>
        /// <returns></returns>
        private TreeViewItem GetSelectedGroupTreeViewItem()
        {
            TreeViewItem tvi = this._tvGroupingAirports.SelectedItem as TreeViewItem;
            if (tvi == null) return null;
            if (tvi.Tag is AirportsGroup) return tvi;
            tvi = tvi.Parent as TreeViewItem;
            if (tvi == null) return null;
            if (tvi.Tag is AirportsGroup) return tvi;
            return null;
        }

        /// <summary>
        /// Select group
        /// </summary>
        /// <returns></returns>
        private AirportsGroup GetSelectedGroup()
        {
            TreeViewItem tvi = this.GetSelectedGroupTreeViewItem();
            return (tvi == null) ? null : tvi.Tag as AirportsGroup;
        }

        /// <summary>
        /// Group by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private TreeViewItem GetGroupByName(string name)
        {
            if (String.IsNullOrEmpty(name)) return null;
            return this._tvGroupingAirports.Items.OfType<TreeViewItem>().FirstOrDefault(tvi => name.Equals(tvi.Name, StringComparison.InvariantCultureIgnoreCase));
        }

        /// <summary>
        /// Airport groups from tree view
        /// </summary>
        /// <returns></returns>
        private List<AirportsGroup> GetAirportGroups()
        {
            List<AirportsGroup> ags = new List<AirportsGroup>();
            // ReSharper disable LoopCanBeConvertedToQuery
            foreach (var item in this._tvGroupingAirports.Items) {
                TreeViewItem tvi = item as TreeViewItem;
                if (tvi == null || tvi.Tag == null) continue;
                AirportsGroup ag = tvi.Tag as AirportsGroup;
                if (ag != null) ags.Add(ag);
            }
            // ReSharper restore LoopCanBeConvertedToQuery
            return ags;
        }

        /// <summary>
        /// Airport groups set in tree tree view
        /// </summary>
        /// <returns></returns>
        private void SetAirportGroups(List<AirportsGroup> airportGroups = null)
        {
            if (airportGroups == null) airportGroups = this.GeneratorSettings.AirportGroups;
            this._tvGroupingAirports.Items.Clear();

            // ReSharper disable LoopCanBeConvertedToQuery
            foreach (AirportsGroup ag in airportGroups) {
                if (ag.Icaos.NullSafeIsEmpty()) continue;
                TreeViewItem tvGroup = TreeViewUtil.GetTreeViewItem(ag.Name, Properties.Resources.Folder_16x16);
                tvGroup.Tag = ag;
                this._tvGroupingAirports.Items.Add(tvGroup);
                foreach (string icao in ag.Icaos) {
                    TreeViewItem tvAirport = TreeViewUtil.GetTreeViewItem(icao, Properties.Resources.AirportDOT16x16);
                    tvGroup.Items.Add(tvAirport);
                }
            }
            // ReSharper restore LoopCanBeConvertedToQuery
        }

        /// <summary>
        /// Group context menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GroupsContextMenuClick(object sender, System.Windows.RoutedEventArgs e)
        {
            TreeViewItem tvs = this._tvGroupingAirports.SelectedItem as TreeViewItem;
            if (tvs == null) return;
            TreeViewItem tg = this.GetSelectedGroupTreeViewItem();
            AirportsGroup ag = this.GetSelectedGroup();

            // ReSharper disable PossibleUnintendedReferenceComparison
            if (sender == this._miGroupsRemove) {
                //
                // Remove
                //
                TreeViewItem tp = tvs.Parent as TreeViewItem;
                if (tp == null)
                    this._tvGroupingAirports.Items.Remove(tvs);
                else {
                    tp.Items.Remove(tvs);
                }
            }
            else if (sender == this._miGroupsRename) {
                //
                // Rename
                //
                if (ag != null && tg != null) {
                    DialogGroupName dialog = new DialogGroupName {Owner = this, GroupName = ag.Name};
                    dialog.ShowDialog();
                    if (dialog.DialogResult.HasValue && dialog.DialogResult.Value && !String.IsNullOrEmpty(dialog.GroupName)) {
                        string gn = dialog.GroupName;

                        // valid name?
                        if (!AirportsGroup.IsValidGroupName(gn)) {
                            this.WriteStatusMsg(Properties.Resources.GroupInvalidName, ConsoleAndStatus.Severity.Error);
                            return;
                        }

                        // rename
                        ag.Name = gn;
                        TreeViewUtil.RenameItem(gn, tg);
                    }
                }
            }
            else if (sender == this._miGroupsSelect) {
                //
                // Select
                //
                if (ag != null) {
                    this._lbSidStarAirports.SelectedItems.Clear();
                    bool position = false;
                    foreach (var item in this._lbSidStarAirports.Items) {
                        string icao = item.ToString();
                        if (!ag.Icaos.Contains(icao)) continue;
                        this._lbSidStarAirports.SelectedItems.Add(item);
                        if (position) continue;
                        position = true;
                        this._lbSidStarAirports.ScrollIntoView(item);
                    }
                }
            }
            // ReSharper restore PossibleUnintendedReferenceComparison
        }

        /// <summary>
        /// Item changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GroupsContextmenuItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            // not used yet
        }

        /// <summary>
        /// Blacklist items
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FixBlacklistContextMenuClick(object sender, RoutedEventArgs e)
        {
            // http://stackoverflow.com/questions/3373239/wpf-editable-combobox
            string text = this._cbFixBlacklist.Text;
            if (!String.IsNullOrEmpty(text)) text = text.Trim().ToUpperInvariant();
            string selectedItem = this.FixBlackListSelectedOrHighlighted();

            // ReSharper disable PossibleUnintendedReferenceComparison
            if (sender == this._miFixBlacklistClear) {
                this.GeneratorSettings.FixBlacklist.Clear();
            }
            else if (sender == this._miFixBlacklistRemoveSelected && !String.IsNullOrEmpty(selectedItem)) {
                this.GeneratorSettings.FixBlacklist.Remove(selectedItem);
                this.WriteStatusMsg(String.Format(Properties.Resources.BlacklistFixRemoved, selectedItem));
            }
            else if (sender == this._miFixBlacklistAddFix && !String.IsNullOrEmpty(text)) {
                if (!this.GeneratorSettings.FixBlacklist.Contains(text)) {
                    this.GeneratorSettings.FixBlacklist.Add(text);
                    this.WriteStatusMsg(String.Format(Properties.Resources.BlacklistFixAdded, text));
                    this._cbFixBlacklist.Text = "";
                }
            }
            else if (sender == this._miFixBlacklistSort && !this.GeneratorSettings.FixBlacklist.NullSafeIsEmpty()) {
                List<string> sortedFixes = this.GeneratorSettings.FixBlacklist.ToList();
                sortedFixes.Sort();
                this.GeneratorSettings.FixBlacklist.Clear();
                foreach (string fx in sortedFixes) {
                    this.GeneratorSettings.FixBlacklist.Add(fx);
                }
            }
            // ReSharper restore PossibleUnintendedReferenceComparison
        }

        /// <summary>
        /// Enter key for fixes blacklist
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FixBlacklistOnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key != Key.Enter) return;
            RoutedEventArgs re = new RoutedEventArgs();
            this.FixBlacklistContextMenuClick(this._miFixBlacklistAddFix, re);
        }

        /// <summary>
        /// Get the selected or highlighted item
        /// </summary>
        /// <returns></returns>
        private string FixBlackListSelectedOrHighlighted()
        {
            string s = this._cbFixBlacklist.SelectedItem as string;
            return String.IsNullOrEmpty(s) ? null : s.Trim().ToUpperInvariant();
        }
        #endregion Events
    }
}